package com.yonyou.pmclouds.projectlog.rmiimpl;

import com.alibaba.dubbo.config.annotation.Service;
import com.yonyou.pmclouds.ApplicationConsts;
import com.yonyou.pmclouds.basecom.bp.template.SingleInsertBpTemplate;
import com.yonyou.pmclouds.project.entity.ProjectVO;
import com.yonyou.pmclouds.project.rmiitf.ProjectSimpleQuery;
import com.yonyou.pmclouds.projectlog.entity.ProjectLogDetailVo;
import com.yonyou.pmclouds.projectlog.entity.ProjectLogJobVo;
import com.yonyou.pmclouds.projectlog.entity.ProjectLogVo;
import com.yonyou.pmclouds.projectlog.mapper.ProjectLogDetailMapper;
import com.yonyou.pmclouds.projectlog.mapper.ProjectLogMapper;
import com.yonyou.pmclouds.projectlog.rmiitf.ProjectLogMaintain;
import com.yonyou.pmclouds.team.rmiitf.TeamMemberQuery;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * 智能日志队列消费者
 * @author xiejianyu
 * 2019-09-19
 */
@Slf4j
@Service(version = ApplicationConsts.APPLICATION_VERSION, interfaceClass = ProjectLogMaintain.class)
public class ProjectLogMaintainImpl implements ProjectLogMaintain {

/* @Autowired
    private PMCloudCacheManager pmCloudCacheManager;*/
    @Autowired
    private ProjectLogMapper projectLogMapper;
    @Autowired
    private ProjectLogDetailMapper projectLogDetailMapper;
    @Autowired
    private ProjectSimpleQuery projectQuery;//本地bean形式的service调用
    @Autowired
    private TeamMemberQuery teamMemberQuery;//本地bean形式的service调用

/*    public void executeJob(){
        //此方法无限循环处理智能日志
        while(true) {
            try {
                //可以在打印的Log中观察以下info打印信息，判断当前实例是否正在消费队列任务
                log.info("【智能日志队列任务消费者】，等待获取任务......");
                //队列中无元素则会一直等待
                Object obj = pmCloudCacheManager.pollObjFromBlockingQueue(ProjectLogJobVo.JOB_QUEUE_NAME);
                log.info("【智能日志队列任务消费者】，获取任务成功");
                //阻塞队列其实不用判断，但中间有封装，保险起见
                if (obj == null) {
                    //正常不应该出现这种情况
                    log.warn("智能日志job处理线程获取到了null值");
                    continue;
                }
                log.info("【智能日志队列任务消费者】，开始执行任务");
                ProjectLogJobVo jobVo = (ProjectLogJobVo) obj;

            } catch (Exception e) {
                log.error("智能日志消费处理出错", e);
            }
        }
    }*/

    /**
     * 方法中不要使用Runtime相关的任何值，所有需要的值从jobVo中取，不够就在生产者端补充
     * 目前这个方法被server端在一个事务中被同步调用
     *
     * 另一种方案是异步队列方式消费
     * 方法本身按照多消费者并发消费的场景编写，目的为在多消费者的场景下，解决任务消费顺序和事件发生顺序不一致时产生的问题
     * 为达到上述目的，事件生产者端将事件发生的时间记录在任务对象中（取对应业务单据的creationtime或modifytime，与单据本身保持一致性）
     * 在更新明细记录的时候，判断当期事件时间是否比记录新，否则不修改（因为移动端展示相关数据统一都放到json字段中，更新时采用全量覆盖的模式）
     * 具体更新策略看各业务点需求，并不一定都按照上述比较时间的方式（比如更新主记录上的签到计数场景，每单都更新即可。目前明细更新还没有别的场景，有需求的话扩展这个方法）
     * 异步方案需要考虑事件回滚的情况，是否需要捕捉异常时往队列中添加删除的指令，或者保证日志的触发点在远程服务调用的最后一步，之后不会再有出异常的操作。
     */
    @Override
    public void upsert(ProjectLogJobVo jobVo) {
        try {
            ProjectLogVo projectLogVo = projectLogMapper.queryProjectLog(jobVo.getPkTenant(), jobVo.getPkProject(), jobVo.getLogDate());

            boolean hasRecord = true;
            if (projectLogVo == null) {//新建日志主记录
                SingleInsertBpTemplate<ProjectLogVo> bp = new SingleInsertBpTemplate<>(projectLogMapper);
                projectLogVo = new ProjectLogVo();
                projectLogVo.setPkTenant(jobVo.getPkTenant());
                projectLogVo.setPkProject(jobVo.getPkProject());

                ProjectVO currProject = projectQuery.queryProject(jobVo.getPkProject());
                if (currProject == null) {
                    log.error("智能日志主记录生成出错，查询不到项目信息，项目pk：{}", jobVo.getPkProject());
                    return;
                }
                projectLogVo.setProjectName(currProject.getSname());
                projectLogVo.setProjectCode(currProject.getScode());
                projectLogVo.setProjectState(currProject.getProjectState());

                int currentProjectMember = teamMemberQuery.countProjectMembers(jobVo.getPkProject());
                projectLogVo.setProjectMemberCount(currentProjectMember);

                if (jobVo.getEventType()==ProjectLogJobVo.EVENT_TYPE_SIGN_IN) {
                    projectLogVo.setSignInCount(1);
                }
                projectLogVo.setLogDate(jobVo.getLogDate());

                projectLogVo.setCreator(jobVo.getUserId());
                projectLogVo.setCreationtime(jobVo.getEventTime());
                projectLogVo.setModifier(jobVo.getUserId());
                projectLogVo.setModifytime(jobVo.getEventTime());

                try {
                    projectLogVo = bp.insert(projectLogVo);
                    hasRecord = false;
                } catch (Exception e) {
                    // TODO: 2019/9/23 有优化空间，详见下面明细部分的说明
                    log.warn("智能日志新建主记录出现异常，如果这个异常不是数据库唯一约束冲突造成的，则需要优化这个地方了", e);
                    projectLogVo = projectLogMapper.queryProjectLog(jobVo.getPkTenant(), jobVo.getPkProject(), jobVo.getLogDate());
                }
            }

            String pkProjectLog = projectLogVo.getPkProjectLog();

            //签到事件
            if (jobVo.getEventType()==ProjectLogJobVo.EVENT_TYPE_SIGN_IN) {
                if (hasRecord) {
                    projectLogMapper.increaseSignInCountById(pkProjectLog);
                }
                log.info("【智能日志队列任务消费者】，任务执行完毕");
                return;
            }

            if (jobVo.getEventType()==ProjectLogJobVo.EVENT_TYPE_DETAIL) {
                ProjectLogDetailVo projectLogDetailVo = (ProjectLogDetailVo)(jobVo.getAttachment());
                projectLogDetailVo.setPkProjectLog(pkProjectLog);

                //这个唯一查询其实还是依赖了pkContentSource的全局唯一性
                ProjectLogDetailVo oldVo = projectLogDetailMapper.queryProjectLogDetailByTypeAndContentSource(pkProjectLog, projectLogDetailVo.getDetailType(), projectLogDetailVo.getPkContentSource());
                if (oldVo==null) {//新建明细记录
                    SingleInsertBpTemplate<ProjectLogDetailVo> detailBp = new SingleInsertBpTemplate<>(projectLogDetailMapper);
                    try {
                        detailBp.insert(projectLogDetailVo);
                        log.info("【智能日志队列任务消费者】，任务执行完毕");
                        return;
                    } catch (Exception e) {
                        // TODO: 2019/9/23  有优化空间
                        //这个地方主要目的为捕捉插入新记录时有可能出现的唯一索引冲突(暂时认为主键不会冲突)，即表中的组合唯一索引冲突(业务层面上的)
                        //因为此处没有使用行级业务锁，用锁的话逻辑可能更麻烦(锁是在外部缓存中间件中，数据库事务在这一时刻不一定提交了，当前线程需要等待，之后还要重新走一遍先查是否有记录再尝试获取锁的逻辑)
                        //只有insert会产生异常，因此到这里的走不到return，且insert并未成功
                        //重新查询是否已存在记录，然后按照更新的逻辑走

                        //这里有个潜在的坑，目前使用bpTemplate进行的插入操作，模板内部封装且改变了异常对象，且有可能产生除了DB层的额外异常
                        //按照以上构想，只有DuplicateKeyException类型才能走此逻辑(即只catch这个异常类型)
                        //所以目前暂时先catch所有异常，观察下线上运行情况，如果因此产生了错误则考虑不用模板操作插入，直接使用mapper进行操作
                        log.warn("智能日志新建明细记录出现异常，如果这个异常不是数据库唯一约束冲突造成的，则需要优化这个地方了", e);
                        oldVo = projectLogDetailMapper.queryProjectLogDetailByTypeAndContentSource(pkProjectLog, projectLogDetailVo.getDetailType(), projectLogDetailVo.getPkContentSource());
                    }
                }
                //以下是更新的逻辑，新建正常的话走不到这里
                projectLogDetailMapper.updateProjectLogDetailContentLatestById(oldVo.getPkProjectLogDetail(), projectLogDetailVo.getContentJson(), projectLogDetailVo.getModifier(), projectLogDetailVo.getModifytime());
                log.info("【智能日志队列任务消费者】，任务执行完毕");
            }
        } catch (Exception e) {
            log.error("智能日志消费处理出错", e);
        }
    }

}
